//
//  NSArrayAdditions.m
//  HigherOrderMessaging
//
//  Created by Ofri Wolfus on 24/09/05.
//  Copyright 2005 Ofri Wolfus. All rights reserved.
//

#import "NSArrayHOMAdditions.h"
#import <Foundation/NSArray.h>
#import "HOMUtilities.h"
#include <objc/objc-runtime.h>
#import "HOMIteratedArgument.h"
#include <malloc/malloc.h>


//Pointer to objectAtIndex: method of NSArray.
//This was already declared in HOMIteratedVarWrapper.h
//typedef id (* IMPWithUnsignedArg)(id, SEL, unsigned);


/*@interface NSArray (HOM_Private)
- (id)getObjectsWhere:(NSArray *)messages returned:(BOOL)resultValue;
- (id)getResultsForInvocation:(NSInvocation *)invocation NSNullForNil:(BOOL)useNSNull;
- (id)getResultsForSelector:(SEL)sel args:(marg_list)args NSNullForNil:(BOOL)useNSNull;
- (id)singleSelect:(NSInvocation *)invocation;
- (id)singleSelectWhere:(NSArray *)invocations;
//- (id)pickSingle:(NSInvocation *)invocation value:(BOOL)returnValue;
- (id)pickSingleWithResult:(BOOL)returnValue selector:(SEL)sel args:(marg_list)args;
- (id)singleReject:(NSInvocation *)invocation;
- (id)getObjectWhere:(NSArray *)messages returned:(BOOL)resultValue;
@end*/

//Some function prototypes
//The objc compiler automatically casts objc_msgSend() to the currect type, and since we do it by hand,
//we have to also cast it ourselves.
/*typedef BOOL (* hom_BOOL_msgSendv)(id self, SEL op, unsigned arg_size, marg_list arg_frame);
typedef char (* hom_char_msgSendv)(id self, SEL op, unsigned arg_size, marg_list arg_frame);
typedef double (* hom_double_msgSendv)(id self, SEL op, unsigned arg_size, marg_list arg_frame);
typedef float (* hom_float_msgSendv)(id self, SEL op, unsigned arg_size, marg_list arg_frame);
typedef int (* hom_int_msgSendv)(id self, SEL op, unsigned arg_size, marg_list arg_frame);
typedef long (* hom_long_msgSendv)(id self, SEL op, unsigned arg_size, marg_list arg_frame);
typedef long long (* hom_long_long_msgSendv)(id self, SEL op, unsigned arg_size, marg_list arg_frame);
typedef short (* hom_short_msgSendv)(id self, SEL op, unsigned arg_size, marg_list arg_frame);
typedef unsigned char (* hom_unsigned_char_msgSendv)(id self, SEL op, unsigned arg_size, marg_list arg_frame);
typedef unsigned int (* hom_unsigned_int_msgSendv)(id self, SEL op, unsigned arg_size, marg_list arg_frame);
typedef unsigned long (* hom_unsigned_long_msgSendv)(id self, SEL op, unsigned arg_size, marg_list arg_frame);
typedef unsigned long long (* hom_unsigned_long_long_msgSendv)(id self, SEL op, unsigned arg_size, marg_list arg_frame);
typedef unsigned short (* hom_unsigned_short_msgSendv)(id self, SEL op, unsigned arg_size, marg_list arg_frame);

typedef long double (* hom_long_double_msgSendv)(id self, SEL op, unsigned arg_size, marg_list arg_frame);	//Not supported by NSNumber
//#warning NSNumber doesn't support long double yet. We store long doubles in NSValue for now, but a better solution is needed.*/

//Pointer to objectAtIndex: method of NSArray
typedef id (* IMPWithUnsignedArg)(id, SEL, unsigned);

@implementation NSArray (HOMIteration)

extern Method _hom_findMethodForSelector(SEL sel);

- (id)each {
	return [HOMIteratedArgument iteratedArgumentWithArray:self];
}

// Should this be changed to be a varadic method?
- (id)collect:(Message *)msg {
	SEL				sel = [msg selector];
	marg_list		origArgs = [msg arguments];
	unsigned		argsSize = [msg argumentsSize];
	marg_list		args = malloc(malloc_size(origArgs));
	unsigned		argCount = hom_getNumberOfArguments(sel);
	unsigned		i;
	Class			iteratedArgCls = [HOMIteratedArgument class];
	unsigned		count = [self count];
	char			*types = calloc(argCount, sizeof(char));
	Method			meth = _hom_findMethodForSelector(sel);
	NSMutableArray	*result = nil;
	IMP				addObject = NULL;
	IMP				objectAtIndex = [self methodForSelector:@selector(objectAtIndex:)];
	
	if (count > 0U) {
		
		if (*(method_getTypeEncoding(meth)) == _C_ID || *(method_getTypeEncoding(meth)) == _C_CLASS) {
			result = [[[NSMutableArray alloc] initWithCapacity:count] autorelease];
			addObject = [result methodForSelector:@selector(addObject:)];
		}
		
		// First, copy the arguments list since we're going to modify it
		memcpy(args, origArgs, malloc_size(args));
		
		// Scan the arguments of our message and find instances of HOMIteratedArgument.
		// Our types array will mark the places of these with the letter 'z' so we don't have to check again.
		// This loop also finds the largest number of arguments that needs to be iterated, and stores it in count.
		for (i = 2U; i < argCount; i++) {
			types[i] = *hom_getArgumentTypeAtIndex(meth, i);
			if (types[i] == _C_ID) {
				if ([marg_getValue(args, hom_getArgumentOffsetAtIndex(meth, i), id) isKindOfClass:iteratedArgCls])
					types[i] = 'z';	// This is our special mark for an iterated argument
			}
		}
		
		// Loop through all objects in all our iterated arguments, update our args list, and resend it
		for (i = 0U; i < count; i++) {
			unsigned j;
			
			// Update the remaining arguments in our list
			for (j = 2U; j < argCount; j++) {
				if (types[j] == 'z') {
					int offset = hom_getArgumentOffsetAtIndex(meth, j);
					HOMIteratedArgument *arg = marg_getValue(origArgs, offset, HOMIteratedArgument *);
					
					marg_setValue(args, offset, id, [arg nextObject]);
					
					// When there are no arguments left for this argument iterator, we mark its type as id
					// so we'll skip it in the next run.
					if ([arg isFinished])
						types[j] = _C_ID;
				}
			}
			
			// Send the updated arguments list
			addObject(result, @selector(addObject:), objc_msgSendv(objectAtIndex(self, @selector(objectAtIndex:), i), sel, argsSize, args));
		}
		
		// Clean up
		free(types);
		free(args);
	}
	
	return result ?: self;
}

//========================================================
//===================== Select Where =====================
//========================================================
#pragma mark Select Where

- (id)getObjectsWhereFirstMessage:(Message *)firstMsg additionalMessages:(va_list)messages returned:(BOOL)resultValue {
	Message				*msg;
	unsigned			i, count = [self count];
	IMPWithUnsignedArg	objectAtIndexIMP = (IMPWithUnsignedArg)[self methodForSelector:@selector(objectAtIndex:)];
	va_list				msges;
	// Allocating with a non-zero capacity is probably a good idea here.
	// TODO: Do it!
	CFMutableArrayRef	result = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
	
	// Loop through all our objects, and send it all the messages passed to us.
	// All messages except the last are assumed to return id, and the last one should return a BOOL
	// (although any integer value will work).
	for (i = 0U; i < count; i++) {
		id	object = objectAtIndexIMP(self, @selector(objectAtIndex:), i);
		// Send the first message to our object and store the result.
		// Since id is a pointer, and a pointer can hold BOOL values,
		// we're fine even if this is the only message.
		id	obj = objc_msgSendv(object, [firstMsg selector], [firstMsg argumentsSize], [firstMsg arguments]);
		
		// Send every other messages to the previous result.
		// We assume the result is an object. If it's not, bad things will happen, but that's not our fault.
		// NOTE: We don't verify that the receiver responds to the actual message like -select does
		// which give us a nice speed boost, but may also cause an exception to be thrown.
		// TODO: Cache the IMPs of -[Message selector], -[Message argumentsSize] and -[Message arguments].
		va_copy(msges, messages);
		
		while ((msg = va_arg(msges, Message *)))
			obj = objc_msgSendv(obj, [msg selector], [msg argumentsSize], [msg arguments]);
		
		va_end(msges);
		
		// Check if the final result equals the result value we're looking for.
		// If true, we'll add the original object to our results array.
		if ((BOOL)(int)obj == resultValue)
			CFArrayAppendValue(result, object);
	}
	
	// Clean up
	va_end(messages);
	
	// Make our return value pretty
	if (CFArrayGetCount(result) == 0) {
		CFRelease(result);
		result = nil;
	} else {
		result = (CFMutableArrayRef)[(NSArray *)result autorelease];
	}
	
	
	return (id)result;
}

- (id)selectWhere:(Message *)firstMessage, ... {
	va_list				va;
	
	DPAssert(firstMessage != nil, @"The passed message must not be nil");
	va_start(va, firstMessage);
	return [self getObjectsWhereFirstMessage:firstMessage additionalMessages:va returned:YES];
}

- (id)rejectWhere:(Message *)firstMessage, ... {
	va_list				va;
	
	DPAssert(firstMessage != nil, @"The passed message must not be nil");
	va_start(va, firstMessage);
	return [self getObjectsWhereFirstMessage:firstMessage additionalMessages:va returned:NO];
}

//==============================================================
//================= Select/Reject Single Where =================
//==============================================================
#pragma mark Select/Reject Single Where

- (id)getObjectWhereFirstMsg:(Message *)firstMsg additionalMessages:(va_list)messages returned:(BOOL)resultValue index:(unsigned *)index {
	Message				*msg;
	unsigned			i, count = [self count];
	IMPWithUnsignedArg	objectAtIndexIMP = (IMPWithUnsignedArg)[self methodForSelector:@selector(objectAtIndex:)];
	va_list				msges;
	
	// Loop through all our objects, and send it all the messages passed to us.
	// All messages except the last are assumed to return id, and the last one should return a BOOL
	// (although any integer value will work).
	for (i = 0U; i < count; i++) {
		id	object = objectAtIndexIMP(self, @selector(objectAtIndex:), i);
		// Send the first message to our object and store the result.
		// Since id is a pointer, and a pointer can hold BOOL values,
		// we're fine even if this is the only message.
		id	obj = objc_msgSendv(object, [firstMsg selector], [firstMsg argumentsSize], [firstMsg arguments]);
		
		// Send every other messages to the previous result.
		// We assume the result is an object. If it's not, bad things will happen, but that's not our fault.
		// NOTE: We don't verify that the receiver responds to the actual message like -select does
		// which give us a nice speed boost, but may also cause an exception to be thrown.
		// TODO: Cache the IMPs of -[Message selector], -[Message argumentsSize] and -[Message arguments].
		va_copy(msges, messages);
		
		while ((msg = va_arg(msges, Message *)))
			obj = objc_msgSendv(obj, [msg selector], [msg argumentsSize], [msg arguments]);
		
		va_end(msges);
		
		// Check if the final result equals the result value we're looking for.
		// If true, we'll return the original object + its index.
		if ((BOOL)(int)obj == resultValue) {
			if (index)
				*index = i;
			
			return object;
		}
	}
	
	// Clean up
	va_end(messages);
	
	return nil;
}

- (id)selectSingleWhere:(Message *)firstMessage, ... {
	va_list				va;
	
	DPAssert(firstMessage != nil, @"The passed message must not be nil");
	va_start(va, firstMessage);
	return [self getObjectWhereFirstMsg:firstMessage additionalMessages:va returned:YES index:NULL];
}

- (id)rejectSingleWhere:(Message *)firstMessage, ... {
	va_list		va;
	id			object;
	unsigned	i;
	
	DPAssert(firstMessage != nil, @"The passed message must not be nil");
	va_start(va, firstMessage);
	object = [self getObjectWhereFirstMsg:firstMessage additionalMessages:va returned:YES index:&i];
	
	if (object) {
		NSMutableArray	*arr = [self mutableCopy];
		[arr removeObjectAtIndex:i];
		return [arr autorelease];
	}
	
	return nil;
}

@end

@implementation NSArray (HOMAdditions)

//Invoke an invocation with any of our objects
- (void)makeObjectsPerformInvocation:(NSInvocation *)invocation {
	SEL	selector = [invocation selector];
	
	if (invocation) {
		for (unsigned i = 0; i < [self count]; ++i) {
			id	object = [self objectAtIndex:i];
			
			if ([object respondsToSelector:selector])
				[invocation invokeWithTarget:object];
		}
	} else {
		NSException *exception = [NSException exceptionWithName:NSInvalidArgumentException
														 reason:@"invocation must not be nil!"
													   userInfo:nil];
		[exception raise];
	}
}

@end

@implementation NSArray (BlocksSupport)

/*- (void)foreach:(Block)b {
	if (b) {
		if (!block_is_initialized(b)) {
			unsigned *count = malloc(sizeof(unsigned));
			unsigned *index = malloc(sizeof(unsigned));
			
			*count = [self count];
			*index = 0U;
			block_set_value_for_key(b, count, @"count");
			block_set_value_for_key(b, index, @"index");
			block_set_value_for_key(b, [self objectAtIndex:0U], @"each");
			block_process(b);
		} else {
			unsigned *count = block_get_value_for_key(b, @"count");
			unsigned *index = block_get_value_for_key(b, @"index");
			
			++(*index);
			
			if (*index < *count) {
				block_set_value_for_key(b, [self objectAtIndex:*index], @"each");
				block_process(b);
			} else {
				free(count);
				free(index);
				//block_free(b);
			}
		}
	}
}*/

#if 0
- (void)foreach:(DPBlock *)b {
	unsigned i, count = [self count];
	
	for (i = 0U; i < count; i++) {
		[b setValue:[self objectAtIndex:i]
			 forKey:@"each"];
		[b process];
	}
}
#endif
			

@end
